﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;

namespace webjies.Utilities
{
    //Subject: How to combine with 'OR' or 'AND' operators two or more lambda expressions
    //Abstract: 
    //Tags: LINQ, Expression, Lambda, C#, "Entity Framework", .NET
    //Section: LINQ
    //Author: 

    /// <summary>
    /// Combine two expression into one using AndAlso or OrElse. Works only if the expressions have the same parameters types.
    /// This class must be used because when you have two or more lamda expressions and you need to combine them with 'OR' or 'AND' operator.
    /// Using Expression.AndAlso and Expression.OrElse is not sufficient because you must also allign the parameters.
    /// </summary>
    public static class ExpressionCombine
    {
        public static Expression<T> AndAlso<T>(Expression<T> a, Expression<T> b)
        {
            Expression bodyA;
            Expression bodyNew;
            ReplaceParameters<T>(a, b, out bodyA, out bodyNew);

            return Expression.Lambda<T>(Expression.AndAlso(bodyA, bodyNew), a.Parameters);
        }
        public static Expression<T> OrElse<T>(Expression<T> a, Expression<T> b)
        {
            Expression bodyA;
            Expression bodyNew;
            ReplaceParameters<T>(a, b, out bodyA, out bodyNew);

            return Expression.Lambda<T>(Expression.OrElse(bodyA, bodyNew), a.Parameters);
        }

        static void ReplaceParameters<T>(Expression<T> a, Expression<T> b, out Expression bodyA, out Expression bodyNew)
        {
            bodyA = a.Body;
            bodyNew = b.Body;

            for (int i = 0; i < a.Parameters.Count; i++)
            {
                bodyNew = ExpressionReplacer.ReplaceParameter(b.Parameters[i], a.Parameters[i], bodyNew);
            }
        }
    }
    /// <summary>
    /// Replace the parameters of an expression with the parameters of another expression.
    /// </summary>
    public class ExpressionReplacer : ExpressionVisitor
    {
        private readonly Expression candidate_;
        private readonly Expression replacement_;

        ExpressionReplacer(Expression candidate, Expression replacement)
        {
            candidate_ = candidate;
            replacement_ = replacement;
        }

        public override Expression Visit(Expression node)
        {
            return node == candidate_ ? replacement_ : base.Visit(node);
        }

        public static Expression ReplaceParameter(Expression parameterToFind, Expression parameterToReplace, Expression body)
        {
            return new ExpressionReplacer(parameterToFind, parameterToReplace).Visit(body);
        }
    }
}
